import sha, hmac, urllib, re, time, imghdr, os, base64

from google.appengine.api import urlfetch
from google.appengine.ext import db
from google.appengine.ext.webapp import template

from model import *
import config

def unmap(series_name = False, contest_name = False, submission_name = False):
    series = None
    contest = None
    submission = None
    if series_name:
        series = Series.gql('WHERE short_name = :1', series_name).get()
        if series and contest_name:
            contest = series.contest_set.filter('short_name =',contest_name).get()
            if contest and submission_name:
                submission = contest.submission_set.filter('short_name =',submission_name).get()
    return series, contest, submission

def render(req, template_file, series_name = '', contest_name = '', submission_name = '', token = ''):
    (series, contest, submission) = unmap(series_name, contest_name, submission_name)
    user = get_user(req)
    if series and not series.visible and contest and not contest.visible and submission and not submission.visible:
        return ''
    return template.render(template_file, {
        'series_list': Series.all(),
        'series': series,
        'contest': contest,
        'submission': submission,
        'user': user,
        'path': req.path,
        'headless': req.get('headless') == '1',
        'token': token
    })

def csrf_check(req, res, force = True):
    token_sent = req.get('token')
    token = ''
    if not req.cookies.has_key('token'):
        token = base64.b64encode(os.urandom(16)).strip()
        res.headers.add_header('Set-Cookie', 'token=%s;httpOnly;path=/' % token )
        if force:
            raise 'Missing CSRF token'
    else:
        token = req.cookies['token']
        if token_sent != token:
            if force:
                raise str('Wrong CSRF token %s vs. %s' % (token_sent, token))
    return token

def signup(req):
    sig = req.get('sig')
    signed = ('nickname','profile','group','posts','days','avatar')
    sigr = []
    
    for elem in signed:
        sigr.append(urllib.quote_plus(req.str_GET.get(elem), safe = ''))
    
    cs = sha.new('/'.join(sigr)).hexdigest()
    signature = sha.new(config.SHARED_SECRET + cs).hexdigest()
    
    if not sig == signature:
        raise str('Invalid Signature %s vs. %s : %s' % (sig, signature, '/'.join(sigr)))
    
    kn = sha.new(req.get('profile')).hexdigest()
    us = ForumUser.get_or_insert(
        key_name = kn,
        nickname = unicode(req.str_GET.get('nickname'),'iso-8859-1'),
        profile = req.get('profile'),
        group = req.get('group'),
        posts = int(req.get('posts') or 0),
        days = int(req.get('days') or 0)
    )
    us.nickname = unicode(req.str_GET.get('nickname'),'iso-8859-1')
    us.profile = req.get('profile')
    us.group = req.get('group')
    us.posts = int(req.get('posts') or 0)
    us.days = int(req.get('days') or 0)
    
    if req.get('avatar'):
        avatar = LinkedImage.get_or_insert(
            key_name = 'avatar:'+us.profile,
            url = req.get('avatar')
        )
        avatar.url = req.get('avatar')
        avatar.put()
        us.avatar = avatar
    
    us.put()
    avatar.creator = us
    avatar.put()
    return us

def get_user(req, force = False):
    if req.cookies.has_key('login'):
        cookie = req.cookies.get('login')
        (type, tstamp, key, signature) = cookie.split('/')
        correct = hmac.new(config.PRIVATE_SECRET, '%s/%s/%s' % (
            urllib.quote(type),
            urllib.quote(tstamp),
            urllib.quote(key)
        )).hexdigest()
        if correct == signature:
            # TODO: check tstamp?
            usr = User.get(db.Key(key))
            if usr.kind() == 'User':
                return usr
    if force:
        raise 'Invalid session cookie'

def set_user(usr, res):
    type = usr.type
    tstamp = time.time()
    sig = hmac.new(config.PRIVATE_SECRET, '%s/%s/%s' % (
        urllib.quote(type),
        urllib.quote(str(tstamp)),
        urllib.quote(usr.key().__str__())
    )).hexdigest()
    res.headers.add_header('Set-Cookie', 'login=%s/%s/%s/%s;%s;path=/;httpOnly' % (
        urllib.quote(type),
        urllib.quote(str(tstamp)),
        urllib.quote(usr.key().__str__()),
        sig,
        time.strftime(
            'expires=%a, %d-%b-%y %H:%M:%S GMT', time.localtime(tstamp + 60 * 60 * 24 * 365)
        )
    ))

def error(type, obj):
    raise type

def clean_short_name(name):
    short_name = re.sub(r'[^a-zA-Z0-9-]+','_',name)
    return short_name

def put_submission(req, series_name, contest_name, submission_name = False):
    usr = get_user(req, True)
    (series, contest, submission) = unmap(series_name, contest_name, submission_name)
    if series.visible and contest.visible:
        rlz = series.rules.submission_reqs
        if submission:
            if usr.key().__str__() != submission.creator.key().__str__():
                return error('permission', usr)
        elif rlz.maximum:
            ents = usr.baseentity_set
            count = 0
            for ent in ents:
                if ent.contest.key().__str__() == contest.key().__str__():
                    count = count + 1
            if count >= rlz.maximum:
                return error('requirements', rlz)
            if rlz.min_posts and usr.posts < rlz.min_posts:
                return error('requirements', rlz)
            if rlz.min_days and usr.days < rlz.min_days:
                return error('requirements', rlz)
            if rlz.groups and not (usr.group in rlz.groups):
                return error('requirements', rlz)
        name = req.get('name')
        description = req.get('description')
        if name == '' or description == '':
            raise 'Missing required fields.'
        if not submission:
            short_name = o_short_name = clean_short_name(name)
            (s,c,b) = unmap(series_name, contest_name, short_name)
            i = 0
            while b:
                i = i + 1
                short_name = o_short_name + '_' + str(i)
                (s,c,b) = unmap(series_name, contest_name, short_name)
        links = []
        linked_imgs = []
        if submission:
            links = submission.links
        elems = []
        rlinks = req.get_all('links')
        for link in rlinks:
            if link:
                # this links could be images
                if link.endswith(config.ALLOWED_IMAGES):
                    img = LinkedImage(
                        creator=usr,
                        url=config.IMAGE_PROXY % urllib.quote(link),
                        original_url = link,
                        caption = link
                    )
                    elems.append(img)
                    linked_imgs.append(img)
                    img.put()
                else:
                    links.append(db.Link(link))
        videos = []
        if submission:
            videos = submission.videos
        rvideos = req.get_all('videos')
        for video in rvideos:
            if video:
                videos.append(db.Link(video))
        try:
            if not submission:
                submission = Submission(
                    contest = contest,
                    creator = usr,
                    visible = not series.rules.hidden,
                    short_name = short_name,
                    name = name,
                    description = description,
                    links = links,
                    videos = videos
                )
                elems.append(submission)
            else:
                submission.name = name
                submission.description = description
                submission.links = links
                submission.videos = videos
            submission.put()
            # process uploaded images, if any.
            avatar = None
            images = req.POST.multi.getall('avatar_file')
            for image in images:
                if isinstance(image, str):
                    continue
                if len(image.value) > 1024*1024:
                        raise str('File %s is too large' % image.filename )
                if submission.image_set.count() >= config.MAX_IMAGES:
                    raise 'Too many images'
                if imghdr.what('',image.value) in config.ALLOWED_IMAGES:
                    avatar = UploadedImage(
                        creator = usr,
                        object = submission,
                        caption = image.filename,
                        data = image.value,
                        type = image.type
                    )
                    elems.append(avatar)
                    avatar.put()
                    avatar.url = 'https://ehnvote.appspot.com/i/%s' % avatar.key().__str__()
                    avatar.put()
                    break
            if not avatar:
                avatar_key = req.get('avatar_opt')
                if avatar_key:
                    k = db.Key(avatar_key)
                    avatar = Image.get(k)
            if avatar:
                submission.icon = avatar;
                submission.put()
            if req.POST.has_key('images'):
                images = req.POST.multi.getall('images')
                for image in images:
                    if isinstance(image, str):
                        continue
                    if len(image.value) > 1024*1024:
                        raise str('File %s is too large' % image.filename )
                    if submission.image_set.count() >= config.MAX_IMAGES:
                        raise 'Too many images'
                    if imghdr.what('',image.value) in config.ALLOWED_IMAGES:
                        img = UploadedImage(
                            creator = usr,
                            object = submission,
                            caption = image.filename,
                            data = image.value,
                            type = image.type
                        )
                        elems.append(img)
                        img.put()
                        img.url = 'https://ehnvote.appspot.com/i/%s' % img.key().__str__()
                        img.put()
            # and set object to linked images
            for image in linked_imgs:
                if submission.image_set.count() >= config.MAX_IMAGES:
                    raise 'Too many images'
                image.object = submission
                image.put()
        except:
            for elem in elems:
                elem.delete()
            raise
        return submission
